package tpcomp2010.estructura;

import java.util.ArrayList;

/**
 *  Clase que simula el comportamiento de un automata, ya sea AFN, AFD o AFD min.
 *  Contiene la lista de nodos del automata, el nodo inicial, los nodos finales,
 *  el tipo del automata y la expresion regular que representa.
 */

public class Automata {

    /*Lista de nodos del automata*/
    private ListaNodos nodos;

    /*Nodo inicial del Automata*/
    private Nodo nodoInicial;

    /*Lista de los nodos finales del automata*/
    private ListaNodos nodosFinales;

    /*La expresion Regular representada por el Automata*/
    private String expRegular;

    /*Alfabeto*/
    private ArrayList<String> alfabeto;
    private String simboloVacio = Constantes.getSimboloVacio();

    /** Constructor por defecto de la clase Automata.
     */
    public Automata() {
        this.nodos = new ListaNodos();
        this.nodosFinales = new ListaNodos();
    }

    /** Constructor de un Automata a partir de un simbolo del alfabeto.
     *  Crea dos nodos nuevos y una arista que los une con el simbolo como
     *  etiqueta .
     */
    public Automata(String simbolo) {
        this.nodos = new ListaNodos();

        Nodo nuevo1 = new Nodo(0, true, false, false);
        Nodo nuevo2 = new Nodo(1, false, true, false);
        Arista enlace = new Arista(nuevo1, nuevo2, simbolo);
        nuevo1.agregarArista(enlace);

        this.nodos.insertar(nuevo1);
        this.nodos.insertar(nuevo2);

        this.nodoInicial = nuevo1;
        this.nodosFinales = new ListaNodos();
        this.nodosFinales.add(nuevo2);
    }

    /* Operacion de Thompson or "|". Crea dos nuevos nodos que conecta el inicio
     * de ambos automatas que realizan la operacion.
     * @param auto2 Automata con el cuala se hace la operacion "|"
     * 
     */
    public void or(Automata auto2) {
        Automata auto1 = this;

        Nodo finalAuto1 = auto1.getNodosFinales().getNodo(0);
        Nodo finalAuto2 = auto2.getNodosFinales().getNodo(0);
        Nodo inicialAuto1 = auto1.getNodoInicial();
        Nodo inicialAuto2 = auto2.getNodoInicial();
        finalAuto1.setNodofinal(false);
        finalAuto2.setNodofinal(false);
        Nodo nuevoInicial = new Nodo(0, true, false, false);
        Nodo nuevoFinal = new Nodo(auto1.nodos.size() + auto2.nodos.size() + 1, false, true, false);
        auto1.nodoInicial.setNodoinicial(false);
        auto2.nodoInicial.setNodoinicial(false);

        auto1.reenumerar(1);
        auto2.reenumerar(auto1.nodos.size() + 1);

        nuevoInicial.agregarArista(new Arista(nuevoInicial, inicialAuto1, this.simboloVacio));
        nuevoInicial.agregarArista(new Arista(nuevoInicial, inicialAuto2, this.simboloVacio));
        finalAuto1.agregarArista(new Arista(finalAuto1, nuevoFinal, this.simboloVacio));
        finalAuto2.agregarArista(new Arista(finalAuto2, nuevoFinal, this.simboloVacio));

        for (Nodo nodo : auto2.nodos) {
            auto1.nodos.insertar(nodo);
        }

        auto1.nodos.insertar(nuevoInicial);
        auto1.nodos.insertar(nuevoFinal);

        auto1.nodoInicial = nuevoInicial;
        auto1.getNodosFinales().set(0, nuevoFinal);
    }

    
    /** Operacion de Thompson concat "."
     *  Hace que el nodo final de auto1 sea el nodo inicial de auto2.
     * @param auto2 Automata con el cuala se hace la operacion "."
     *
     */
    public void concatenacion(Automata auto2) {
        Automata auto1 = this;

        Nodo finalAuto1 = auto1.getNodosFinales().getNodo(0);
        Nodo inicialAuto2 = auto2.getNodoInicial();

        inicialAuto2.setNodoinicial(false);
        finalAuto1.setNodofinal(false);

        int nodoFinalAuto1ID = auto1.nodos.size() - 1;
        auto2.reenumerar(nodoFinalAuto1ID);

        //hacer que todos las aristas del nodo inicial de auto2, pertenezcan al
        //nodo final de auto1
        for (Arista arista : inicialAuto2.getAristas()) {
            arista.setOrigen(finalAuto1);
            finalAuto1.agregarArista(arista);
        }
        //hacer que todos los nodos que apuntaban al nodo inicial apunten al
        //nuevo nodo inicial
        for (Nodo nodoAuto2 : auto2.nodos) {
            for (Arista aristaActual : nodoAuto2.getAristas()) {
                Nodo nodoDestinoActual = aristaActual.getNodoDestino();
                if (nodoDestinoActual.getId() == inicialAuto2.getId()) {
                    aristaActual.setNodoDestino(finalAuto1);
                }
            }

            if (nodoAuto2.getId() != inicialAuto2.getId()) {
                auto1.nodos.insertar(nodoAuto2);
            }
        }
        auto1.getNodosFinales().set(0, auto2.getNodosFinales().getNodo(0));
    }

    /** Operacion que crea un nuevo nodo inicial que enlaza con el actual
     * inicial a traves de una arista con cadena vacia, y un nuevo final
     * que enlaza con el final actual a traves de un enlace con cadena vacia.
     *
     */
    public void crearNodosAristasNuevas() {
        Automata auto1 = this;
        auto1.reenumerar(1);

        Nodo nuevoInicial = new Nodo(0, true, false, false);
        Nodo nuevoFinal = new Nodo(auto1.nodos.size() + 1, false, true, false);

        Nodo actualInicial = auto1.getNodoInicial();
        Nodo actualFinal = auto1.getNodosFinales().getNodo(0);

        actualInicial.setNodoinicial(false);
        actualFinal.setNodofinal(false);

        nuevoInicial.agregarArista(new Arista(nuevoInicial, actualInicial, this.simboloVacio));

        actualFinal.agregarArista(new Arista(actualFinal, nuevoFinal, this.simboloVacio));

        this.nodoInicial = nuevoInicial;
        this.nodosFinales.set(0, nuevoFinal);

        auto1.nodos.insertar(nuevoInicial);
        auto1.nodos.insertar(nuevoFinal);
    }

    /** Operacion que simula la operacion "?" (cero uno).
     * Se crean un nuevo nodo inicial y un nuevo nodo final, y
     * se crea un enlace con cadena vacia desde el nuevo nodo inicial hasta el
     * nuevo nodo final para representar cero o uno.
     */
    public void ceroUno() {
        this.crearNodosAristasNuevas();
        this.nodoInicial.agregarArista(new Arista(this.nodoInicial, this.nodosFinales.getNodo(0), this.simboloVacio));
    }

    /** Operacion que simula la operacion "+" (uno o mas).
     * Se crean un nuevo nodo inicial enlazado al inicial original con simbolo 
     * vacio, un nuevo nodo final enlazado al final original con simbolo vacio,
     * y se crea un enlace con cadena vacia desde el nodo final original hasta
     * el nodo inicial original.
     */
    public void mas() {
        Nodo inicioOriginal = this.nodoInicial;
        Nodo finOriginal = this.getNodosFinales().getNodo(0);

        this.crearNodosAristasNuevas();
        finOriginal.agregarArista(new Arista(finOriginal, inicioOriginal, this.simboloVacio));
    }

    /** Operacion que simula la operacion "*" (cero o mas).
     * Se crean un nuevo nodo inicial enlazado al inicial original con simbolo
     * vacio, un nuevo nodo final enlazado al final original con simbolo vacio,
     * un enlace con cadena vacia desde el nodo final original hasta el nodo
     * inicial original y un nuevo enlace desde el nuevo nodo inicial hasta
     * el nuevo nodo final.
     */
    public void asterisco() {
        Nodo inicioOriginal = this.nodoInicial;
        Nodo finOriginal = this.nodosFinales.get(0);

        this.crearNodosAristasNuevas();

        finOriginal.agregarArista(new Arista(finOriginal, inicioOriginal, this.simboloVacio));
        this.nodoInicial.agregarArista(new Arista(this.nodoInicial, this.nodosFinales.getNodo(0), this.simboloVacio));
    }

    public ListaNodos getNodos() {
        return this.nodos;
    }

    /** Obtiene un nodo por su id
     * @param id identificador del nodo.
     * @return nodo el nodo con el identificador recibido.
     * 
     */
    public Nodo getNodoById(int id) {
        return this.nodos.getNodoById(id);
    }

    /** Obtiene la lista de nodos finales del automata actual.
     * @return nodosFinales Lista de los nodos finales.
     *
     */
    public ListaNodos getNodosFinales() {
        return nodosFinales;
    }

    /** Obtiene la lista de nodos no finales del automata actual.
     * @return lista Lista de los nodos no finales.
     *
     */
    public ListaNodos getNoFinales() {
        ListaNodos lista = new ListaNodos();
        for (Nodo x : nodos) {
            if (!x.isNodofinal()) {
                lista.insertar(x);
            }
        }
        return lista;
    }

    /** Obtiene el nodo inicial del automata actual.
     * @return nodoInicial Nodo inicial.
     *
     */
    public Nodo getNodoInicial() {
        return nodoInicial;
    }

    public void setInicial(Nodo ini) {
        this.nodoInicial = ini;
    }

    /** Obtiene el alfabeto que acepta el automata actual.
     * @return alfabeto un array con el alfabeto.
     *
     */
    public ArrayList<String> getAlfabeto() {
        return this.alfabeto;
    }

    public String getExpRegular() {
        return this.expRegular;
    }

    public void setAlfabeto(ArrayList<String> alpha) {
        this.alfabeto = alpha;
    }

    public void setExpRegular(String regex) {
        this.expRegular = regex;
    }

    public void reenumerar(int incremento) {
        for (Nodo nodo : this.nodos) {
            nodo.setId(nodo.getId() + incremento);
        }
    }

    public void addNodo(Nodo e) {
        this.nodos.insertar(e);
    }


    /** Retorna un String con la representacion del automata en forma de grafo.
     * @return result la cadena de nodos y enlaces del grafo.
     *
     */
    public String imprimirAutomata() {
        String result = "";
        for (Nodo e : this.nodos) {
            result += "\nNodo." + e.getId();
            if (e.isNodoinicial()) {
                result += "(i)";
            }

            if (e.isNodofinal()) {
                result += "(f)";
            }
            result += "\n";
            for (Arista enlace : e.getAristas()) {
                result += "\t"
                        + enlace.getOrigen().getId() + "=====" + enlace.getEtiqueta() + "====> "
                        + enlace.getNodoDestino().getId() + "\n";
            }
        }
        return result;
    }

}
